package com.tools.middle.redis;

import com.tools.common.container.AbsOwnList;
import com.tools.common.container.AbsOwnSet;
import com.tools.common.container.SetEnum;
import com.tools.common.exception.DataGenerationException;
import com.tools.common.exception.NotFoundDataException;
import com.tools.common.object.Note;
import com.tools.common.object.string.VerifyKit;
import redis.clients.jedis.HostAndPort;

import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 自定义 Redis 配置项
 * 包装实体类
 * */
@Note("自定义 Redis 配置项包装实体类")
public class RedisSource {

    @Note("格式为 【ip/主机名:端口号】 的节点对象的 LinkedHashSet 集合")
    private LinkedHashSet<HostAndPort> hostAndPortSet;

    @Note("Redis 连接的最大连接数")
    private int maxTotal = 100;

    @Note("Redis 连接的最大空闲连接")
    private int maxIdle = 30;

    @Note("Redis 连接的最小空闲连接")
    private int minIdle = 10;

    @Note("Redis 等待获取连接的阻塞最大等待时间，单位是毫秒，负值表示不限制")
    private int maxWait = 2000;

    @Note("Redis 连接超时时间，单位毫秒")
    private int connectionTimeout = 2000;

    @Note("Redis socket 连接的超时时间，单位毫秒")
    private int socketTimeout = 2000;

    @Note("数据库的索引，默认是第一个")
    private int databaseIndex = 0;

    @Note("redis 6.0 引入了访问控制（ACL），可以设置用户名了")
    private String username;

    @Note("Redis 若开启权限验证，则此处为验证的授权密码")
    private String password;

    @Note("Redis 客户端名称")
    private String clientName = "JedisClient-00";

    @Note("Redis 的最大重试次数")
    private int maxAttempts = 5;

    @Note("主节点名称。如果这个不为 null 则可以通过这个加上节点集合创建哨兵集群")
    private String masterName;

    /* **********************************************************************************
     *
     *          构造器
     *
     * *********************************************************************************
     * */

    public RedisSource() {}

    public static RedisSource of() {
        return new RedisSource();
    }

    @Note("读取 properties 里的配置项，转为实例返回")
    public static RedisSource from(Map properties) {
        RedisSource source = new RedisSource();
        if(properties == null || properties.isEmpty()) return source;
        source.setMaxTotal((Integer) properties.get("redis-jedis-maxTotal"));
        source.setMaxIdle((Integer) properties.get("redis-jedis-maxIdle"));
        source.setMinIdle((Integer) properties.get("redis-jedis-minIdle"));
        source.setMaxWait((Integer) properties.get("redis-jedis-maxWait"));
        source.setConnectionTimeout((Integer) properties.get("redis-jedis-connection-timeout"));
        source.setSocketTimeout((Integer) properties.get("redis-jedis-socket-timeout"));
        source.setDatabaseIndex((Integer) properties.get("redis-jedis-database"));
        source.setPassword((String) properties.get("redis-jedis-password"));
        source.setMasterName((String) properties.get("redis-jedis-masterName"));
        String hosts = (String) properties.get("redis-jedis-hosts");
        if(hosts == null || hosts.isEmpty()) {
            throw new DataGenerationException("没有扫描到 Redis 节点，请至少配置一个以上的 Redis 节点：【节点ip:节点端口】。" +
                    "多个节点用,分隔：【ip1:端口1,ip2:端口2……】 注意,前后不要有其他字符");
        }
        String[] a = hosts.split(",");
        //单机
        if(a.length < 2) {
            return source.add(a[0]);
        }
        //集群
        return source.addAll(a);
    }

    /* **********************************************************************************
     *
     *          Getter / Setter
     *
     * *********************************************************************************
     * */

    public LinkedHashSet<HostAndPort> getHostAndPortSet() {
        if(this.hostAndPortSet == null || this.hostAndPortSet.isEmpty()) {
            throw new IllegalArgumentException("至少需要配置一个以上的节点");
        }
        return hostAndPortSet;
    }

    public void setHostAndPortSet(LinkedHashSet<HostAndPort> hostAndPortSet) {
        if(hostAndPortSet == null || hostAndPortSet.isEmpty()) {
            throw new IllegalArgumentException("至少需要配置一个以上的节点");
        }
        this.hostAndPortSet = hostAndPortSet;
    }

    public int getMaxTotal() {
        return maxTotal;
    }

    public void setMaxTotal(Integer maxTotal) {
        this.maxTotal = (maxTotal == null || maxTotal <= 0) ? this.maxTotal : maxTotal;
    }

    public int getMaxIdle() {
        return maxIdle;
    }

    public void setMaxIdle(Integer maxIdle) {
        this.maxIdle = (maxIdle == null || maxIdle <= 0) ? this.maxIdle : maxIdle;
    }

    public int getMinIdle() {
        return minIdle;
    }

    public void setMinIdle(Integer minIdle) {
        this.minIdle = (minIdle == null || minIdle <= 0) ? this.minIdle : minIdle;
    }

    public int getMaxWait() {
        return maxWait;
    }

    public void setMaxWait(Integer maxWait) {
        this.maxWait = (maxWait == null || maxWait <= 0) ? this.maxWait : maxWait;
    }

    public int getConnectionTimeout() {
        return connectionTimeout;
    }

    public void setConnectionTimeout(Integer connectionTimeout) {
        this.connectionTimeout = (connectionTimeout == null || connectionTimeout <= 0) ? this.connectionTimeout : connectionTimeout;
    }

    public int getSocketTimeout() {
        return socketTimeout;
    }

    public void setSocketTimeout(Integer socketTimeout) {
        this.socketTimeout = (socketTimeout == null || socketTimeout <= 0) ? this.socketTimeout : socketTimeout;
    }

    public int getDatabaseIndex() {
        return databaseIndex;
    }

    public void setDatabaseIndex(Integer databaseIndex) {
        this.databaseIndex = (databaseIndex < 0) ? this.databaseIndex : databaseIndex;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getClientName() {
        return clientName;
    }

    public void setClientName(String clientName) {
        this.clientName = (clientName == null || clientName.isEmpty()) ? this.clientName : clientName;
    }

    public int getMaxAttempts() {
        return maxAttempts;
    }

    public void setMaxAttempts(Integer maxAttempts) {
        this.maxAttempts = maxAttempts;
    }

    public String getMasterName() {
        return masterName;
    }

    public void setMasterName(String masterName) {
        this.masterName = masterName;
    }

    @Override
    public String toString() {
        return "RedisSource{" +
                "hostAndPortSet=" + hostAndPortSet +
                ", maxTotal=" + maxTotal +
                ", maxIdle=" + maxIdle +
                ", minIdle=" + minIdle +
                ", maxWait=" + maxWait +
                ", connectionTimeout=" + connectionTimeout +
                ", socketTimeout=" + socketTimeout +
                ", databaseIndex=" + databaseIndex +
                ", username=" + username +
                ", password=" + password +
                ", clientName=" + clientName +
                ", maxAttempts=" + maxAttempts +
                ", masterName=" + masterName +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        RedisSource that = (RedisSource) o;
        return databaseIndex == that.databaseIndex && Objects.equals(hostAndPortSet, that.hostAndPortSet);
    }

    @Override
    public int hashCode() {
        return Objects.hash(hostAndPortSet, databaseIndex);
    }

    public boolean isSingle() {
        return hostAndPortSet != null && hostAndPortSet.size() == 1;
    }

    public boolean isCluster() {
        return hostAndPortSet != null && hostAndPortSet.size() > 1;
    }

    public boolean isSentinel() {
        return masterName != null && !masterName.isEmpty();
    }

    public HostAndPort getFirst() {
        return hostAndPortSet.iterator().next();
    }

    /* **********************************************************************************
     *
     *          链式调用
     *
     * *********************************************************************************
     * */

    public RedisSource hostAndPortSet(LinkedHashSet<HostAndPort> hostAndPortSet) {
        this.setHostAndPortSet(hostAndPortSet);
        return this;
    }

    public RedisSource maxTotal(Integer maxTotal) {
        this.setMaxTotal(maxTotal);
        return this;
    }

    public RedisSource maxIdle(Integer maxIdle) {
        this.setMaxIdle(maxIdle);
        return this;
    }

    public RedisSource minIdle(Integer minIdle) {
        this.setMinIdle(minIdle);
        return this;
    }

    public RedisSource maxWait(Integer maxWait) {
        this.setMaxWait(maxWait);
        return this;
    }

    public RedisSource connectionTimeout(Integer connectionTimeout) {
        this.setConnectionTimeout(connectionTimeout);
        return this;
    }

    public RedisSource socketTimeout(Integer socketTimeout) {
        this.setSocketTimeout(socketTimeout);
        return this;
    }

    public RedisSource databaseIndex(Integer databaseIndex) {
        this.setDatabaseIndex(databaseIndex);
        return this;
    }

    public RedisSource username(String username) {
        this.setUsername(username);
        return this;
    }

    public RedisSource password(String password) {
        this.setPassword(password);
        return this;
    }

    public RedisSource clientName(String clientName) {
        this.setClientName(clientName);
        return this;
    }

    public RedisSource maxAttempts(Integer maxAttempts) {
        this.setMaxAttempts(maxAttempts);
        return this;
    }

    public RedisSource masterName(String masterName) {
        this.setMasterName(masterName);
        return this;
    }

    /* **********************************************************************************
     *
     *          自定义方法
     *
     * *********************************************************************************
     * */

    public RedisSource add(String hostAndPort) {
        HostAndPort node = RedisKit.newHostAndPort(hostAndPort);
        private_initHostAndPortSet(1);
        this.hostAndPortSet.add(node);
        return this;
    }

    public RedisSource add(HostAndPort node) {
        if(node == null) throw new NullPointerException("Redis 主机节点实例不能为 null");
        private_initHostAndPortSet(1);
        this.hostAndPortSet.add(node);
        return this;
    }

    public RedisSource addAll(String... hostAndPortArray) {
        if(hostAndPortArray == null) return this;
        int length = hostAndPortArray.length;
        if(length == 0) return this;
        private_initHostAndPortSet(length);
        for(String hostAndPort : hostAndPortArray) {
            HostAndPort node = RedisKit.newHostAndPort(hostAndPort);
            this.hostAndPortSet.add(node);
        }
        return this;
    }

    public RedisSource addAll(HostAndPort... hostAndPortArray) {
        if(hostAndPortArray == null) return this;
        int length = hostAndPortArray.length;
        if(length == 0) return this;
        private_initHostAndPortSet(length);
        for(HostAndPort node : hostAndPortArray) {
            if(node == null) throw new NullPointerException("Redis 主机节点实例不能为 null");
            this.hostAndPortSet.add(node);
        }
        return this;
    }


    public RedisSource addAllString(List<String> hostAndPortList) {
        if(hostAndPortList == null || hostAndPortList.isEmpty()) return this;
        private_initHostAndPortSet(hostAndPortList.size());
        for(String hostAndPort : hostAndPortList) {
            HostAndPort node = RedisKit.newHostAndPort(hostAndPort);
            this.hostAndPortSet.add(node);
        }
        return this;
    }

    public RedisSource addAllNode(List<HostAndPort> hostAndPortList) {
        if(hostAndPortList == null || hostAndPortList.isEmpty()) return this;
        private_initHostAndPortSet(hostAndPortList.size());
        for(HostAndPort node : hostAndPortList) {
            if(node == null) throw new NullPointerException("Redis 主机节点实例不能为 null");
            this.hostAndPortSet.add(node);
        }
        return this;
    }


    public RedisSource addAllString(AbsOwnList<String> hostAndPortList) {
        if(hostAndPortList != null && !hostAndPortList.isEmpty()) {
            private_initHostAndPortSet(hostAndPortList.size());
            for(String hostAndPort : hostAndPortList) {
                HostAndPort node = RedisKit.newHostAndPort(hostAndPort);
                this.hostAndPortSet.add(node);
            }
        }
        return this;
    }

    public RedisSource addAllNode(AbsOwnList<HostAndPort> hostAndPortList) {
        if(hostAndPortList != null && !hostAndPortList.isEmpty()) {
            private_initHostAndPortSet(hostAndPortList.size());
            for(HostAndPort node : hostAndPortList) {
                if(node == null) throw new NullPointerException("Redis 主机节点实例不能为 null");
                this.hostAndPortSet.add(node);
            }
        }
        return this;
    }


    public RedisSource addAllString(LinkedHashSet<String> hostAndPortSet) {
        if(hostAndPortSet == null || hostAndPortSet.isEmpty()) return this;
        private_initHostAndPortSet(hostAndPortSet.size());
        for(String hostAndPort : hostAndPortSet) {
            this.hostAndPortSet.add(RedisKit.newHostAndPort(hostAndPort));
        }
        return this;
    }

    public RedisSource addAllNode(LinkedHashSet<HostAndPort> hostAndPortSet) {
        if(hostAndPortSet == null || hostAndPortSet.isEmpty()) return this;
        private_initHostAndPortSet(hostAndPortSet.size());
        for(HostAndPort node : hostAndPortSet) {
            if(node != null) {
                this.hostAndPortSet.add(node);
                continue;
            }
            throw new NullPointerException("Redis 主机节点实例不能为 null");
        }
        return this;
    }

    public RedisSource addAllString(AbsOwnSet<String> hostAndPortSet) {
        if(hostAndPortSet == null || hostAndPortSet.isEmpty() || hostAndPortSet.getImplEnum() != SetEnum.LINKED) return this;
        private_initHostAndPortSet(hostAndPortSet.size());
        for(String hostAndPort : hostAndPortSet) {
            this.hostAndPortSet.add(RedisKit.newHostAndPort(hostAndPort));
        }
        return this;
    }

    public RedisSource addAllNode(AbsOwnSet<HostAndPort> hostAndPortSet) {
        if(hostAndPortSet == null || hostAndPortSet.isEmpty() || hostAndPortSet.getImplEnum() != SetEnum.LINKED) return this;
        private_initHostAndPortSet(hostAndPortSet.size());
        for(HostAndPort node : hostAndPortSet) {
            if(node != null) {
                this.hostAndPortSet.add(node);
                continue;
            }
            throw new NullPointerException("Redis 主机节点实例不能为 null");
        }
        return this;
    }


    public RedisSource remove(String hostAndPort) {
        if(!VerifyKit.isHostAndPort(hostAndPort)) return this;
        if(hostAndPortSet == null || hostAndPortSet.isEmpty()) return this;
        this.hostAndPortSet.remove(RedisKit.newHostAndPort(hostAndPort));
        return this;
    }

    public RedisSource remove(HostAndPort node) {
        if(node == null) return this;
        if(hostAndPortSet == null || hostAndPortSet.isEmpty()) return this;
        this.hostAndPortSet.remove(node);
        return this;
    }


    public RedisSource removeAll(String... hostAndPortArray) {
        if(hostAndPortArray == null || hostAndPortArray.length == 0) return this;
        if(hostAndPortSet == null || hostAndPortSet.isEmpty()) return this;
        for (String hostAndPort : hostAndPortArray) {
            this.hostAndPortSet.remove(RedisKit.newHostAndPort(hostAndPort));
        }
        return this;
    }

    public RedisSource removeAll(HostAndPort... hostAndPortArray) {
        if(hostAndPortArray == null || hostAndPortArray.length == 0) return this;
        if(hostAndPortSet == null || hostAndPortSet.isEmpty()) return this;
        for (HostAndPort node : hostAndPortArray) {
            this.hostAndPortSet.remove(node);
        }
        return this;
    }

    public RedisSource removeAllString(Iterable<String> hostAndPortArray) {
        if(hostAndPortArray == null) return this;
        if(hostAndPortSet == null || hostAndPortSet.isEmpty()) return this;
        for (String hostAndPort : hostAndPortArray) {
            this.hostAndPortSet.remove(RedisKit.newHostAndPort(hostAndPort));
        }
        return this;
    }

    public RedisSource removeAllNode(Iterable<HostAndPort> hostAndPortArray) {
        if(hostAndPortArray == null) return this;
        if(hostAndPortSet == null || hostAndPortSet.isEmpty()) return this;
        for (HostAndPort node : hostAndPortArray) {
            this.hostAndPortSet.remove(node);
        }
        return this;
    }


    public boolean contains(String hostAndPort) {
        if(!VerifyKit.isHostAndPort(hostAndPort)) return false;
        if(hostAndPortSet == null || hostAndPortSet.isEmpty()) return false;
        return hostAndPortSet.contains(RedisKit.newHostAndPort(hostAndPort));
    }

    public boolean contains(HostAndPort hostAndPort) {
        if(hostAndPort == null) return false;
        if(hostAndPortSet == null || hostAndPortSet.isEmpty()) return false;
        return hostAndPortSet.contains(hostAndPort);
    }

    public HostAndPort firstNode() {
        if(hostAndPortSet == null || hostAndPortSet.isEmpty()) throw new NotFoundDataException("没有找到 Redis 节点");
        return hostAndPortSet.iterator().next();
    }

    public boolean usernameNotEmpty() {
        if (this.passwordNotEmpty()) {
            return username != null && !username.isEmpty();
        }
        return false;
    }

    public boolean passwordNotEmpty() {
        return username != null && !username.isEmpty();
    }

    /* **********************************************************************************
     *
     *          私有逻辑
     *
     * *********************************************************************************
     * */

    @Note("初始化节点集合")
    private void private_initHostAndPortSet(int size) {
        if(hostAndPortSet == null) {
            this.hostAndPortSet = new LinkedHashSet<>((int) (size / 0.75) + 1);
        }
    }
}
